package com.general.framework.data.mongo;

import com.general.framework.core.exception.ViolationException;
import com.general.framework.core.lang.Beans;
import com.general.framework.core.lang.Jsons;
import com.general.framework.core.lang.Numbers;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.util.Assert;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.Collection;
import java.util.Map;

/**
 * @author leejean team
 * @ClassName: BaseMongoRepository
 * @Description: mongo数据基本操作
 * @date 2020年06月08 16:56:17
 */
@Slf4j
@NoRepositoryBean
public abstract class BaseMongoRepository<T, ID> {

    protected MongoTemplate mongoTemplate;

    private MongoPersistentEntity mongoPersistentEntity;

    /**
     * 泛型entity类
     */
    private Class<T> entityClass;

    @Autowired
    public void setMongoTemplate(MongoTemplate mongoTemplate) {
        this.mongoTemplate = mongoTemplate;
        this.init();
    }

    /**
     * 初始化
     */
    private void init() {
        this.entityClass = this.findGenericClass();
        this.mongoPersistentEntity = this.mongoTemplate.getConverter().getMappingContext().getRequiredPersistentEntity(this.entityClass);
    }

    /**
     * 新增
     *
     * @param entity
     */
    public void insert(final T entity) {
        this.preInsert(entity);
        this.mongoTemplate.insert(entity);
    }

    /**
     * 批量增加数据到数据库
     *
     * @param entities
     * @return
     */
    public void insertList(final Collection<T> entities) {
        if (CollectionUtils.isNotEmpty(entities)) {
            entities.forEach((entity) -> this.preInsert(entity));
            this.mongoTemplate.insert(entities, entityClass);
        }
    }

    /**
     * 根据ID查询文档
     *
     * @param id
     * @return
     */
    public T selectById(final ID id) {
        Assert.notNull(id, "The given id must not be null!");
        return mongoTemplate.findById(id, this.entityClass);
    }

    /**
     * 根据ID更新值
     *
     * @param entity
     * @return
     */
    public long updateByIdSelective(T entity) {
        Assert.notNull(entity, "update entity not be empty!");
        final String idName = this.getIdName();
        ID idValue = null;
        try {
            final Field field = entityClass.getDeclaredField(this.getIdName());
            field.setAccessible(true);
            idValue = (ID) field.get(entity);
            final Update update = this.buildUpdate(entity);
            return this.updateById(idValue, update);
        } catch (IllegalAccessException | NoSuchFieldException e) {
            log.error("获取class={}的主键值={}失败, errorMsg={}", entityClass, idName, e);
            throw new RuntimeException("get class id field value error");
        }
    }


    /**
     * 根据ID删除文档
     *
     * @param id
     */
    public void deleteById(final ID id) {
        Assert.notNull(id, "The given id must not be null!");
        mongoTemplate.remove(getIdQuery(id), this.entityClass);
    }

    /**
     * 根据ID集合删除列表
     *
     * @param idList
     * @return
     */
    public long deleteByIdList(final Collection<ID> idList) {
        final Criteria criteria = Criteria.where(getIdName()).in(idList);
        final Query query = Query.query(criteria);
        final DeleteResult result = mongoTemplate.remove(query, this.entityClass);
        return result.getDeletedCount();
    }

    /**
     * 是否存在值
     *
     * @param id
     * @return
     */
    public boolean existsById(final ID id) {
        Assert.notNull(id, "The given id must not be null!");
        return mongoTemplate.exists(getIdQuery(id), this.entityClass);
    }

    /**
     * 根据条件更新数据
     *
     * @param entity
     * @param query
     * @return
     */
    protected long updateBySelective(final T entity, final Query query) {
        Assert.notNull(entity, "update entity not be empty!");
        final Update update = this.buildUpdate(entity);
        final UpdateResult updateResult = this.mongoTemplate.updateMulti(query, update, entityClass);
        return updateResult.getMatchedCount();
    }

    /**
     * 更新ID修改数据
     *
     * @param id
     * @param update
     */
    protected long updateById(ID id, final Update update) {
        Assert.notNull(id, "The given id must not be null!");
        final UpdateResult updateResult = this.mongoTemplate.updateFirst(this.getIdQuery(id), update, entityClass);
        return updateResult.getMatchedCount();
    }

    /**
     * 创建数据保存数据之前额外操作回调方法 默认为空逻辑，子类根据需要覆写添加逻辑即可
     *
     * @param entity 待创建数据对象
     */
    protected void preInsert(final T entity) {
    }

    /**
     * 更新数据保存数据之前额外操作回调方法 默认为空逻辑，子类根据需要覆写添加逻辑即可
     *
     * @param entity 待更新数据对象
     */
    protected void preUpdate(final T entity) {
    }


    /**
     * 获取集合名
     *
     * @return
     */
    protected String getCollectionName() {
        return this.mongoPersistentEntity.getCollection();
    }


    /**
     * 获取ID名称
     *
     * @return
     */
    protected String getIdName() {
        return this.mongoPersistentEntity.getRequiredIdProperty().getName();
    }

    /**
     * @param id
     * @return
     */
    protected Query getIdQuery(final ID id) {
        return Query.query(Criteria.where(this.getIdName()).is(id));
    }

    /**
     * 获取Update语句
     *
     * @param entity
     * @return
     */
    private Update buildUpdate(T entity) {
        this.preUpdate(entity);
        final Map<String, Object> objectMap = Beans.toMap(entity);
        final Update update = new Update();

        boolean isEmpty = true;
        for (Map.Entry<String, Object> entry : objectMap.entrySet()) {
            final Object value = entry.getValue();
            if (value == null) {
                continue;
            }
            final Class<?> clazz = value.getClass();
            if (Collection.class.isAssignableFrom(clazz) && ((Collection) value).size() < Numbers.INTEGER_ONE) {
                continue;
            }
            if (clazz == String.class && Strings.isBlank(value.toString())) {
                continue;
            }
            update.set(entry.getKey(), value);
            isEmpty = false;
        }
        if (isEmpty) {
            log.error("update entity not be empty, objectMap ={}", Jsons.toJsonString(objectMap));
            throw new ViolationException("更新数据不能为空");
        }
        return update;
    }

    /**
     * 查询类的泛型类
     *
     * @return 泛型类
     * @throws ViolationException
     */
    private Class<T> findGenericClass() {
        try {
            // 通过反射取得Entity的Class.
            final Object genericClass = getClass().getGenericSuperclass();
            if (genericClass instanceof ParameterizedType) {
                this.entityClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
                return this.entityClass;
            }
        } catch (final Exception e) {
            log.error("error detail:", e);
        }
        throw new ViolationException("泛型定义没有找到");
    }

}
